Just keeping this on file. I need it way too often and always forget it.
2013/07/15
ffmpeg syntax to extract audio from an mp4
ffmpeg -i <inputfile.mp4> -acodec copy <outfile.aac/.mp3>
2013/06/29
Software Rants 13: Python Build Systems
So after delving into pcsx2 for a week and having the wild ride of a mid-sized CMake project I can officially say any language that makes conditionals require a repetition of the initial statement dumb as hell. But CMake proves a more substancial problem - domain languages that leak, a lot.
Building software is a complex task. You want to call external programs, perform a wide variety of repetitious tasks, do checking, verifying, and on top of that you need to be able to keep track of changes to minimize time to build.
Interestingly, that last point leads me to a tangent - there are 3 technologies that are treated pretty much independently of one another but overlap a lot here. Source control, build management, and packaging all involve the manipulation of a code base and its outputs. Source control does a good job managing changes, build systems create conditional products for circumstance, and packagers prepare the software for deployment.
I think it would be interesting if a build system took advantage of the presence of the other two dependencies of a useful large software project - maybe using git staging to track changes in the build repository. Maybe the build system can prepare packages directly, rather than having an independent packaging framework - after all, you need to recompile most of the time anyway.
But that is aside the point. The topic is build systems - in particular, waf. Qmake is too domain specific and has the exact same issues as make, cmake, autotools, etc - they all start out as domain languages that mutate into borderline turing complete languages because their domain is hugely broad and complex, and it has evolved more complex over time. This is why I love the idea of python based build systems - though at the same time, it occurs to me most python features go unused in a build system and just waste processor cycles too.
But I think building is the perfect domain of scripting languages - python might be slow, but I could care less considering how pretty it is. However, my engagements with waf have made me ask some questions - why does it break traditional pythonic software development wholesale (from bundling the library with source distribution, to expecting fixed name files of wscript that provide functions with some wildcard argument that acts really magical).
What you really want is to write proj.py and use traditional pythonic coding practices with a build system library, probably from pypi, You download the library, do an import buildsystem, or from buildsystem import builder or something, rather than pigeonhole a 2 decade old philosophy of files without extensions in every directory with a fixed name.
Here is an example I'd like to write in this theoretical build system covering pretty much every aspect off the top of my head:
# You can play waf and just stick the builder.py file with the project,
# without any of the extensionless fixed name nonsese.
from builder import recurse, find_packages, gcc, clang
from sys import platform
subdirs = ('sources', 'include', ('subproj', 'subbuilder.py'))
name = 'superproj'
version = '1.0.0'
args = (('install', ret='inst'),)
pkg_names = ('sdl', 'qt5', 'cpack')
builder.lib_search_path += ('/lib','/usr/lib','/usr/local/lib', '~/.lib', '/usr/lib32', '/usr/lib64', './lib')
# Start here, parse the arguments (including optional specifiers in args) a lot of the builder. global members
# can be initialized with this function via default arguments.
todo = builder.init('.', opt=args, build_dir='../build')
if(todo = 'configure'):
# builder packages are an internal class, providing libraries, versioning, descriptions, and headers.
# when you call your compiler, you can supply packages to compile with.
pkgs += builder.find_packages(pkg_names)
pkgs += find_packages('kde4')
utils += builder.find_progs('gcc', 'ld', 'cpp', 'moc')
# Find a library by name, it will do case insensitive search for any library file of system descript,
# like libpulseaudio.so.0.6 or pulseaudio.dll. It would cache found libraries already and not repeat
# itself on subsequent builds.
libs += builder.find_lib('pulseaudio')
otherFunction()
builder.recurse(subdirs)
elif(todo = 'build'):
# You can get environments for various languages from the builder, supplying them with
cpp = builder.env.cpp
py = builder.env.py
qt = builder.env.qt # for moc support
# you can set build dependencies on targets, so if the builder can find these in the project tree
# it builds them first
builder.depends('subproj', 'libproj')
# builder would be aware of sys.platform
if platform is 'linux': # linux building
qt.srcs('main.cpp', 'main.moc')
qt.include('global.hpp')
qt.pkgs = pkgs['qt5']
qt.jobs = 8 # or the .compile syntax
qt.cc = gcc # set the compiler
qt.args = ('-wstring',)
# qt.compile would always run the MOC
qt.compile(jobs=8,cc=gcc,args=self.args+(, '-O2', '-pthread'),warn=gcc.warn.all,out=verbose)
# at this point, you have your .o files generated and dropped in your builder.build_dir directory.
builder.recurse(subdirs, 'build')
if platform is 'darwin': # osx building
if platform is 'win32': # windows building
elif todo='link':
# do linking
elif todo='install':
# install locally
elif tood='pack':
# package for installation, maybe using cpack
Basically, you have a library to enable building locally, and you use it as a procedural order of operations to do so, rather than define black box functions you want some builder program to run. There could also be prepared build objects you could get from such a library, say, builder.preprocess(builder.defaults.qt) would supply an object to parse whatever operation is being incited (so you would use it regardless of the calling function on your script) to do the boilerplate for your choice platform.
I imagine it could go as far as to include anything from defaults.vsp to defaults.django or defaults.cpp or defaults.android. It would search on configure, include on build, and package on pack all the peripheral libraries complementing the choice development platform in one entry line.
The principle concerns with such a schema are mainly performance. You want a dependency build graph in place so you know what you can build in parallel (besides inherently using nprocs forked programs to parse each directory independently, where the root script starts the process, so you need builder.init() in any script that is meant to start a project build, but if you recurse into a subproject that calls that function it doesn't do anything a second time).
You would want to support a lot of ways to deduce changes, besides just hashes, you could use file system modification dates, or maybe even git staging and version differences (ie, a file that doesn't match the current commit version is assumed changed). You would cache such post-changes afterwards. You would probably by default use all available means and the user can turn the up for speedups with potential redundant recompilation (ie, if you move a file, its modification date changes, the old cache is invalidated, but if they hash the same it is assumed the same file moved and isn't recompiled).
If you support build environments, you can support radically different languages. I just think there are some shortcomings in both scons and waf that prevent them from truly taking advantage of their pythonic nature, and using all the paradigms available to python is one of them, I feel.
Building software is a complex task. You want to call external programs, perform a wide variety of repetitious tasks, do checking, verifying, and on top of that you need to be able to keep track of changes to minimize time to build.
Interestingly, that last point leads me to a tangent - there are 3 technologies that are treated pretty much independently of one another but overlap a lot here. Source control, build management, and packaging all involve the manipulation of a code base and its outputs. Source control does a good job managing changes, build systems create conditional products for circumstance, and packagers prepare the software for deployment.
I think it would be interesting if a build system took advantage of the presence of the other two dependencies of a useful large software project - maybe using git staging to track changes in the build repository. Maybe the build system can prepare packages directly, rather than having an independent packaging framework - after all, you need to recompile most of the time anyway.
But that is aside the point. The topic is build systems - in particular, waf. Qmake is too domain specific and has the exact same issues as make, cmake, autotools, etc - they all start out as domain languages that mutate into borderline turing complete languages because their domain is hugely broad and complex, and it has evolved more complex over time. This is why I love the idea of python based build systems - though at the same time, it occurs to me most python features go unused in a build system and just waste processor cycles too.
But I think building is the perfect domain of scripting languages - python might be slow, but I could care less considering how pretty it is. However, my engagements with waf have made me ask some questions - why does it break traditional pythonic software development wholesale (from bundling the library with source distribution, to expecting fixed name files of wscript that provide functions with some wildcard argument that acts really magical).
What you really want is to write proj.py and use traditional pythonic coding practices with a build system library, probably from pypi, You download the library, do an import buildsystem, or from buildsystem import builder or something, rather than pigeonhole a 2 decade old philosophy of files without extensions in every directory with a fixed name.
Here is an example I'd like to write in this theoretical build system covering pretty much every aspect off the top of my head:
# You can play waf and just stick the builder.py file with the project,
# without any of the extensionless fixed name nonsese.
from builder import recurse, find_packages, gcc, clang
from sys import platform
subdirs = ('sources', 'include', ('subproj', 'subbuilder.py'))
name = 'superproj'
version = '1.0.0'
args = (('install', ret='inst'),)
pkg_names = ('sdl', 'qt5', 'cpack')
builder.lib_search_path += ('/lib','/usr/lib','/usr/local/lib', '~/.lib', '/usr/lib32', '/usr/lib64', './lib')
# Start here, parse the arguments (including optional specifiers in args) a lot of the builder. global members
# can be initialized with this function via default arguments.
todo = builder.init('.', opt=args, build_dir='../build')
if(todo = 'configure'):
# builder packages are an internal class, providing libraries, versioning, descriptions, and headers.
# when you call your compiler, you can supply packages to compile with.
pkgs += builder.find_packages(pkg_names)
pkgs += find_packages('kde4')
utils += builder.find_progs('gcc', 'ld', 'cpp', 'moc')
# Find a library by name, it will do case insensitive search for any library file of system descript,
# like libpulseaudio.so.0.6 or pulseaudio.dll. It would cache found libraries already and not repeat
# itself on subsequent builds.
libs += builder.find_lib('pulseaudio')
otherFunction()
builder.recurse(subdirs)
elif(todo = 'build'):
# You can get environments for various languages from the builder, supplying them with
cpp = builder.env.cpp
py = builder.env.py
qt = builder.env.qt # for moc support
# you can set build dependencies on targets, so if the builder can find these in the project tree
# it builds them first
builder.depends('subproj', 'libproj')
# builder would be aware of sys.platform
if platform is 'linux': # linux building
qt.srcs('main.cpp', 'main.moc')
qt.include('global.hpp')
qt.pkgs = pkgs['qt5']
qt.jobs = 8 # or the .compile syntax
qt.cc = gcc # set the compiler
qt.args = ('-wstring',)
# qt.compile would always run the MOC
qt.compile(jobs=8,cc=gcc,args=self.args+(, '-O2', '-pthread'),warn=gcc.warn.all,out=verbose)
# at this point, you have your .o files generated and dropped in your builder.build_dir directory.
builder.recurse(subdirs, 'build')
if platform is 'darwin': # osx building
if platform is 'win32': # windows building
elif todo='link':
# do linking
elif todo='install':
# install locally
elif tood='pack':
# package for installation, maybe using cpack
Basically, you have a library to enable building locally, and you use it as a procedural order of operations to do so, rather than define black box functions you want some builder program to run. There could also be prepared build objects you could get from such a library, say, builder.preprocess(builder.defaults.qt) would supply an object to parse whatever operation is being incited (so you would use it regardless of the calling function on your script) to do the boilerplate for your choice platform.
I imagine it could go as far as to include anything from defaults.vsp to defaults.django or defaults.cpp or defaults.android. It would search on configure, include on build, and package on pack all the peripheral libraries complementing the choice development platform in one entry line.
The principle concerns with such a schema are mainly performance. You want a dependency build graph in place so you know what you can build in parallel (besides inherently using nprocs forked programs to parse each directory independently, where the root script starts the process, so you need builder.init() in any script that is meant to start a project build, but if you recurse into a subproject that calls that function it doesn't do anything a second time).
You would want to support a lot of ways to deduce changes, besides just hashes, you could use file system modification dates, or maybe even git staging and version differences (ie, a file that doesn't match the current commit version is assumed changed). You would cache such post-changes afterwards. You would probably by default use all available means and the user can turn the up for speedups with potential redundant recompilation (ie, if you move a file, its modification date changes, the old cache is invalidated, but if they hash the same it is assumed the same file moved and isn't recompiled).
If you support build environments, you can support radically different languages. I just think there are some shortcomings in both scons and waf that prevent them from truly taking advantage of their pythonic nature, and using all the paradigms available to python is one of them, I feel.
2013/06/24
Magma Rants 5: Imports, Modules, and Contexts
One of the largest issues in almost any language is the trifecta of imports, packaging, and versioning. For Magma, I want it to be a well thought out design that enables portable, compartmentalized code, interoperability between code, and the ability to import both precompiled and compiled object code.
First, we inherit the nomenclature of import <parent>:<child>, where internally referencing such a module is through the defined <parent>:<child> namespacing. Imports are filesystem searched, first locally (with a compiler limited depth, blacklist, and whitelist available) then on the systems import and library paths. You can never define a full pathname import to a static filesystem object with the import clause, but the internal plumbing in std:module includes the necessary woodwork to do raw module loading.
The traditional textual headers and binary libraries process still works. You don't want to bloat deployment libraries with development headers, though if possible I'd make it an option. Magma APIs, with the file suffix of .mapi, are the primary way to provide an abstract view of a library implementation.
In general practice though, we want to avoid the duplication of work in writing headers and source files for every part of a program to speed up compile times. This is mostly a build system problem, in that you want to verify (via hash) a historic versioning of each module, so if it changes its hash you know to recompile it. This means you should should write APIs for libraries or externalized code - which is what a c++ header really should be for.
In addition, an API only describes public member data - you don't need to describe the memory layout of an object in an API so that the compiler can resolve how to allocate address space, you just specify the public accessors. When you compile a shared object, the public accessors are placed in a forward table that a linker just needs to import out. Note that since a library can contain multiple api declarations in one binary, the format also has a reference table to the API indexing arrays.
The workflow becomes one of importing APIs where needed, and using compiler flags and environment variables to search and import the library describing that api. One interesting prospect might be to go the other way - to require compiled libraries be named the same as their apis, and to have one api point to one binary library with one allocator table. It would mean a lot of smaller libraries, but that actually makes some sense. It also means you don't need a seperate linker declaration because any imported api will have a corresponding (for the linkers sake) compiled binary of the same name in the library search path.
I really like that approach - it also introduces the possibility of delayed linking, so that a library isn't linked in until its accessed, akin to how memory pages work in virtual memory. You could also have asynchronous linking, where accessing the libraries faculties before it is pulled into memory causes a lock. Maybe an OS feature?
As a thought experiment I'm going to document what I think are all the various flaws in modern shared object implementations and how to fix them in Altimit / Magma:
First, we inherit the nomenclature of import <parent>:<child>, where internally referencing such a module is through the defined <parent>:<child> namespacing. Imports are filesystem searched, first locally (with a compiler limited depth, blacklist, and whitelist available) then on the systems import and library paths. You can never define a full pathname import to a static filesystem object with the import clause, but the internal plumbing in std:module includes the necessary woodwork to do raw module loading.
The traditional textual headers and binary libraries process still works. You don't want to bloat deployment libraries with development headers, though if possible I'd make it an option. Magma APIs, with the file suffix of .mapi, are the primary way to provide an abstract view of a library implementation.
In general practice though, we want to avoid the duplication of work in writing headers and source files for every part of a program to speed up compile times. This is mostly a build system problem, in that you want to verify (via hash) a historic versioning of each module, so if it changes its hash you know to recompile it. This means you should should write APIs for libraries or externalized code - which is what a c++ header really should be for.
In addition, an API only describes public member data - you don't need to describe the memory layout of an object in an API so that the compiler can resolve how to allocate address space, you just specify the public accessors. When you compile a shared object, the public accessors are placed in a forward table that a linker just needs to import out. Note that since a library can contain multiple api declarations in one binary, the format also has a reference table to the API indexing arrays.
The workflow becomes one of importing APIs where needed, and using compiler flags and environment variables to search and import the library describing that api. One interesting prospect might be to go the other way - to require compiled libraries be named the same as their apis, and to have one api point to one binary library with one allocator table. It would mean a lot of smaller libraries, but that actually makes some sense. It also means you don't need a seperate linker declaration because any imported api will have a corresponding (for the linkers sake) compiled binary of the same name in the library search path.
I really like that approach - it also introduces the possibility of delayed linking, so that a library isn't linked in until its accessed, akin to how memory pages work in virtual memory. You could also have asynchronous linking, where accessing the libraries faculties before it is pulled into memory causes a lock. Maybe an OS feature?
As a thought experiment I'm going to document what I think are all the various flaws in modern shared object implementations and how to fix them in Altimit / Magma:
- You need headers to a library to compile with, and a completely foreign binary linkable library or statically included library to link in at build or run time.
- You need to describe the complete layout of accessible objects and functions in a definition of a struct or class, so that the compiler knows the final size of an object type.
- You need to make sure the header inclusions and library search path contained the desired files, even on disparate runtime environments.
- Symbol tables in binaries can be large and cumbersome to link at runtime and can create sizable load times.
2013/06/06
Magma Rants 4: Containers and Glyphs
Containers are the most core pervasive aspect of any languages long term success. In Magma, since () denotes scope blocks (and can be named), and [] are only used for template declarations, {} and [] are available stand alone to act as container aliases like in Python. [] is an std:array, the primitive statically sized array of homogenous elements. If it has multiple types in one array, it uses std:var as the array type and uses the natural conversion from[object] conversion available in var if it a user defined type, or an overridden more precise conversion function.
{X, X, X} is for unique sets, and {(X,Y),(X,Y)} is for maps. In the same line of thinking, the language tries to find a common conversion type these objects fit in (note: the compiler won't trace the polymorphic inheritance tree to try to find a common ancestor) and casts them, or throws them in vars. The indexing hash functions for sets and maps that determine uniqueness are well defined for std types and you can implement your own as a template override of std:hash[T](T), which needs to return a uint.
Python (since I love Python) also includes the immutable list type () as a tuple, but since Magma [] is already a static contiguous std:array and not an std:dynArray, there is no performance benefit. Note that, like everying in Magma, [] and {} are implied const and can be declared muta {} or muta [] to construct a mutable version.
One of the principle goals I had in thinking about Magma is that a majority of languages overload and obfuscate the implication of glyph patterns, which makes compilation time consuming in a complex parser since syntax is very situational depending on the surrounding text in a source file. Additionally, any time you use multiple sequential glyphs to represent some simple concept (equality as ==, scope as ::, // for comments) I feel is a failure of the language to properly balance glyph allocation and behavior. Albeit, in the current documentation on Magma, I'm using == for logical equality because I turned = back into the assignment operator instead of :, solely on the basis because += is way too baked into my brain to see +: and not think that is strange, and it allowed me to use : for scope and . for property access (which are different, Java).
In conceptualizing Magma, I drafted out all the available glyphs on a standard keyboard and assigned those to language functions. As a result, glyphs like $ became available substitutes for traditional named return in other languages, and made function declarations more obvious because you declare a return type in a function definition (or fn).
{X, X, X} is for unique sets, and {(X,Y),(X,Y)} is for maps. In the same line of thinking, the language tries to find a common conversion type these objects fit in (note: the compiler won't trace the polymorphic inheritance tree to try to find a common ancestor) and casts them, or throws them in vars. The indexing hash functions for sets and maps that determine uniqueness are well defined for std types and you can implement your own as a template override of std:hash[T](T), which needs to return a uint.
Python (since I love Python) also includes the immutable list type () as a tuple, but since Magma [] is already a static contiguous std:array and not an std:dynArray, there is no performance benefit. Note that, like everying in Magma, [] and {} are implied const and can be declared muta {} or muta [] to construct a mutable version.
One of the principle goals I had in thinking about Magma is that a majority of languages overload and obfuscate the implication of glyph patterns, which makes compilation time consuming in a complex parser since syntax is very situational depending on the surrounding text in a source file. Additionally, any time you use multiple sequential glyphs to represent some simple concept (equality as ==, scope as ::, // for comments) I feel is a failure of the language to properly balance glyph allocation and behavior. Albeit, in the current documentation on Magma, I'm using == for logical equality because I turned = back into the assignment operator instead of :, solely on the basis because += is way too baked into my brain to see +: and not think that is strange, and it allowed me to use : for scope and . for property access (which are different, Java).
In conceptualizing Magma, I drafted out all the available glyphs on a standard keyboard and assigned those to language functions. As a result, glyphs like $ became available substitutes for traditional named return in other languages, and made function declarations more obvious because you declare a return type in a function definition (or fn).
2013/06/05
Magma Rants 3: Powerful variance and generics
Magma uses the same compile time template checking that C++ uses - templates (defined with square braces [] in class and function definitions). The distinction between polymorphism and templates is, I feel, still valuable, and unlike Go, I don't see anything inherently wrong with native compiled templates in the C++ vein - if a template usage doesn't support, at compile time, the functions and typing the template uses, it is a compiler error. The implementation will try to coerce types using the object generic functions object.to[T](T) and object.from[T](T), if either direction is defined (because either class could define the conversion to another type) the cast is done. This avoids the ambiguity of dynamic casting in C++, because there is a well defined set of potential casts for every object and the only difference between static_cast and dynamic_cast are if the casts themselves are implemented as const or not. Const casting still exists but requires the "undefined" context to allow undefined behavior (ie, mutating a passed-in const object can be very bad). Const cast is found in std:undefined:ConstCast().
From the other direction, Magma contains std:var, which is the stratos autoboxing container type. It is used pervasively as a stand in for traditional polymorphic [Object] passing because you can retrieve the object from a var with compile time guarantees and type safety, and var includes a lot of additional casting functionality not found in native Magma casts in strings and numbers. If you have a heterogeneous collection, you almost always want the contents to be vars, unless you have a shared common restricting ancestor to denote a subset of objects. You can still query the type of a var, and it delegates all interactions with it besides the ? operator to the descendant. If you really need to call query() / ?, call var:get[T](). You can also get the contents get function by getting it and calling get on it.
Magma also has the auto keyword as a type specifier to reduce an rvalue equality, in the same way C++ does. It statically reduces type from an rvalue statement and parses as such.
From the other direction, Magma contains std:var, which is the stratos autoboxing container type. It is used pervasively as a stand in for traditional polymorphic [Object] passing because you can retrieve the object from a var with compile time guarantees and type safety, and var includes a lot of additional casting functionality not found in native Magma casts in strings and numbers. If you have a heterogeneous collection, you almost always want the contents to be vars, unless you have a shared common restricting ancestor to denote a subset of objects. You can still query the type of a var, and it delegates all interactions with it besides the ? operator to the descendant. If you really need to call query() / ?, call var:get[T](). You can also get the contents get function by getting it and calling get on it.
Magma also has the auto keyword as a type specifier to reduce an rvalue equality, in the same way C++ does. It statically reduces type from an rvalue statement and parses as such.
2013/06/02
Magma Rants 2: Low level abstractions in the base language
One thing that I don't like in modern low level language design is how you easily hang yourself on aged constructs from the 70s, like jumps, and how valuable glyphs like the colon are consumed to maintain a very niche feature. As a result, the base language itself is just the core components of modern language paradigms, with a lot of traditional behavior that goes rarely used and can easily play a gotcha on a developer. Here is a collection of traditionally core language features in C and its ilk that are available only under the System context:
- std:bit contains bitwise operations (bit and and or, shift left and right, bitwise negation). Many std classes like bitfield, flags, and the compiler in the presence of multiple same-scope booleans will use bitwise arithmetic and bit operations but they aren't user facing because a user rarely needs them. The traditional "or" flags syntax of FLAG1 | FLAG2 | FLAG3 is instead a function of the flags addition, in the form FLAG1 + FLAG2 + FLAG3, and - a flag means to remove it from the bitfield.
- std:flow imports enable (as a compiler feature) goto, continue, break, and label. They take the form of functions, std:goto(label), std:label(name), std:continue(), and std:break().
- std:ptr contains the raw pointer construct ptr[T], and the alloc() and free() functions.
- std:asm introduces the asm(TYPE) {} control block to inline assembly instructions in Magma code.
The base language is thus memory safe, doesn't enable bit overflow of variables, and has consistent control flow. This functionality is stil available for those who need it, but excluded for the consideration of any large project that wants to avoid the debugging nightmares that emerge from using these low level tools.
2013/05/25
Game Rants 3: Neverwinter: Part 3: Endgame
Endgame is when you hit 60, right after all the leveling content becomes extremely hard to solo with the stock level 15 companions (yay). I put in the majority of my time here before running out of things to do and quitting.
Part 3: The End Game
Pros:
Because the setting is great. The atmosphere is ok (it doesn't come close to Baldur's Gate quality atmosphere, but that had a lot more work put into characterization - with the lack of NPC persistence in NW, and your companions just being dumb meat bricks, you have no overarching engagement besides this distant threat of Valindra).
Part 3: The End Game
Pros:
- Lots of dungeons to do via the inclusion of epic difficulty.
- PVP gear is easy to obtain, so it doesn't devolve into "full pvp geared player crushes noob" for too long.
- Zone events become very much worth doing as soon as you aren't farming mobs to get enchantments. This gives you some modicum of reason to go back to the last 3 zones.
- Astral diamond acquisition becomes solely a game of playing the auction house (also a con).
- Combat is dynamic, if limited. PVP engagements can be skillful, but they can also be painfully RNG (so also a con).
- Professions and worshiping are long run, low commit engagements to keep people logging in.
- Gearscore requirements on epics mean you won't get undergeared players ruining runs.
Cons:
- Weapon / Armor enchants are purely pay to win. With 1% success rates, but a Greater Tenebrous enchant adding upwards of thousands of DPS to your output, more than any blue -> epic upgrade, and the extreme rarity of any weapon or armor slot enchantment, you end up with absurd AD prices on the AH that normal players can't realistically obtain.
- In PVE, there are only 5 mans. The queue system still doesn't insist on a healer (even though they are mandatory) so you pretty much have to make premades or take a hella long time queuing, getting borked group compositions, and requeueing again. Gauntlgrym should be out this month, and fix this, so it isn't that bad.
- What is bad is that every single PVE encounter with a number of exceptions I could count on my fingers is exactly the same, even at epic difficulty. Don't stand in red areas, constant add spawns, with boatloads of adds at 75 / 50 / 25%. Repeat. That is it almost every single boss fight. The PVE is very flat in that it mechanically is rote.
- Like in every other freaking MMO ever, health does not scale on gear relative to raw damage increases from weapon upgrades. This means unlike the pvp at any other level, max level pvp in epics is very zergy with opportunities to one shot players. This never happens while leveling because the ratio of base health to damage on weapons that were green or blue was in check, but at max level this will get even worse since only select gear gives minute max health increases, but weapon damage (and the raw bulk of other dps stats combined) increases burst and dps significantly each tier.
- Mounts completely break PVP. This is really bad at 60, where you end up doing a lot of bgs, and keep cursing at the pay to win nonsense that is 110% speed mounts. They give you a tremendous competitive advantage when you can backcap over and over all game and nobody can keep up with you because you spent real money on a mount to win bgs.
- Players by max level obtain absurd amounts of CC, and there is no DR on any of it. As a consequence, almost any 2 v 1 engagement is always going to be one player being unable to move while dying in seconds. The existance of latency makes it completely random if someones dodge ability is making them immune to some spell now, or making them immune 5 seconds from now. People get so many evade rolls they can just spam it until they get away. The lack of any choice paradigm in ability usage means you just unload your encounters, maybe a daily, run away, recharge, repeat. You have no reason to stay engaged in a fight, especially if you unloaded on someone who hasn't been able to retaliate yet.
- Gearscore requirements on epics are very stringent and I feel excessive - most of these dungeons have no enrage timers, and the only requirement is people not get killed by avoiding damage. And avoiding damage doesn't come quicker with more gear. The gearscore requirements in effect artificially gate content by making you run lower level dungeons for gear not because you need it to defeat bosses but because the game says you have to do attempt those bosses.
- Converting astral diamond from zen isn't that bad. I'd prefer Cryptic to make the money off a "gold" selling industry (since in effect, astral diamond is the gold of NW). The problem that is bad is that every single pve dungeon drop is BOE, which means you can pay to win buy a full set of the best anything for real life money. This breaks the morale of any competitive PVE scene possible and makes progression pointless.
- Astral diamond prices in-game do not reflect the reality of AD acquisition rates. Nightmare lockbox horses, that are a tiny chance to get and $1.50 a key to unlock, sell for as much as 80% mount speed training and half of 110% speed training, and give you both, plus the mount. The combined cost of both mount skills purely through AD and not considering the real economy valuation of zen to AD puts just getting mount training at almost 3 times more expensive than buying a 110% mount that gives you the skill. This is pervasive of anything that costs diamond - respecs, scrolls, etc - all cost an order of magnitude more than they should, at least. This disconnect cheapens the experience and dissuades players.
- Likewise, zen prices are absurd, and completely out of hand. $40 companions, $50 mounts, $10 single bags on one character. Outrageous, and if Cryptic doesn't make fistfulls of money on NW, it is because they priced the item shop so poorly it alienated a huge portion of the playerbase from buying anything. Not only did they give away all the work (the leveling content) for free, and make it fun, the end game doesn't keep you engaged for long and you can walk away, just like in SWTOR, taking the bulk of their effort with no incentive to pay for anything. That is a recipe for disaster.
- Aggro is still broken for clerics, who will instantly pull aggro doing anything and then never lose it. Tanks are optional, but healers are not.
So overall, the pve is predictable and dull, without any raid content yet. The dracolich fight in Castle Never is, surprisingly, killing adds again. Go figure. PVP is the only real challenge, and that is entirely pay to win between the buying of Tenebrous + armor / weapon enchants, and the mounts. Damage outscaled hp and control abilities became too abundant, so you get two shot without being able to play. Clerics have limited pvp utility - they can use their blue shields to prevent you from taking almost any damage, but require pay to win levels of itemization to keep someone alive through healing.
The real problem is that there is no carrot. Tier 2 gear might take a long time to get farming epics (heroics) but you don't even need it to clear anything. If not for the gearscore requirements, you could do every instance in blues (unless your dps is so low you can't keep the add waves down) because there are no enrage mechanics, nothing that has to be tanked, and clerics pull all the aggro anyway.
You can get full pvp gear in a day, realize it requires absurd pay to win nonsense to get the epic enchants, and quit.
There was a lot of potential ruined by the pay to win aspects and an excessive focus on the leveling process. But once you see the zones once, you are pretty much done - there is no alternative progression path, the story and zones are linear. So you play to max, play a few days, do the dungeons and pvp, and you are completely finished. Which is never a good thing for an MMO.
I'm hoping added content and balancing make Neverwinter good again, but some very fundamental mechanics - spammable immunity dodges, 8 buttons max, aggro mechanics that don't focus on tanks holding threat, the ability to just dodge spam avoid almost all damage, the lack of CC DR, and an excess of CC at that, contribute to some pretty big flaws to keep the game from reaching its potential.
Because the setting is great. The atmosphere is ok (it doesn't come close to Baldur's Gate quality atmosphere, but that had a lot more work put into characterization - with the lack of NPC persistence in NW, and your companions just being dumb meat bricks, you have no overarching engagement besides this distant threat of Valindra).
I'd like to see a refactoring of player abilities to prompt more choice paradigm behavior - making in combat decisions on what to do besides "hit the button and get rooted or not" and beyond picking a daily to use. The best MMO mechanics evolve from players have to chose between offense and defense, burst or sustained, etc, in combat - not just outside it when you pick your spells. The cooldown based usage and obvious situations to use encounter powers cheapen the gameplay. The ability for everyone to avoid so much damage and go immune to effects so often cheapen role specialization. Every dpser gets some aoe (even if rogues aoe sucks), they get some control, they get some utility, etc - it makes them jacks of all trades where you should have aggressive specialization and dependence on other players to form bonds of engagement. Especially in D&D, where you classically needed a bunch of different roles to realistically combat a wide array of threats.
It was the most fun MMO I've played since SWTOR, so props. But it is still full of holes I can't wait to see filled (I'd love to become a lead developer on an MMO some day to put my ideas to the test).
2013/05/21
Magma Rants 1: Introduction to the Language
I've been doing a bit of a thought experiment recently, around the idea of programming languages. I imagine the vast majority of programmers do this, so I don't think I'm special here, and while I would love for this idea to go somewhere, the pressing need for a sustainable consistent influx of cash is more important than solving big problems and fixing the world.
I have a manifesto writeup going on on a Google Doc, found here. I regularly add sections over time, the real pain is when I redo parts of the spec because I don't like the way something is petering out. Here are some examples of that:
The point of this blog series isn't to write the manfiesto, but to brainstorm aspects of the langauge ideas I have by writing them down.
I have a manifesto writeup going on on a Google Doc, found here. I regularly add sections over time, the real pain is when I redo parts of the spec because I don't like the way something is petering out. Here are some examples of that:
- Initially, I liked the idea of using colons as assignment, such that in constructors you would use foo : 5, in variables you would use float bar : 3.5, etc. I gave up on this, for two reasons - one, even though the precedent of using an equals sign for assignment isn't really mathematically accurate (and I really liked the idea of not needing the terrible == for equality) some operations like +: and -: just look ugly with colons attached. Blame my great aesthetic design mindset. This also opens up the colon to be another dedicated glyph, and let me separate property access (.) from scope (:) which I prefer.
The purpose of Magma is to solve a problem - which I hope most languages aim to accomplish - by accepting a reality of native language design. You want a kitchen sink that you can write firmware or a kernel in, but want to actually build a working project with it. I think my approach is a step towards solving this problem, in that the Magma specification outlines multiple effective compiler specifications, with different error conditions and warnings depending on the chosen context. Contexts are compiler flags specifying what kind of binary you are constructing, and the compiler builds each library and executable in its own context, which is an optional header in its metadata specification (depending of course on if you want to compile this to say, LLVM, or some newer binary format for a new architecture). The default context is the application context, but the language specification has multiple contexts:
- system - allows raw bitwise, raw shifts, raw pointer manipulation, jumps, and pointer assignment to integers and casting between the various integers (pointers, ints, fixed width chars) won't produce compiler warnings or errors. You can also use asm: blocks to write inline assembly. This state also outright disables exception handling and any bounds safety checks in standard library classes. This context is meant for kernels and firmware, and should be used sparingly. Even a kernel proper should have most of its libraries written in another context. It is also a good idea to isolate system context code in its own libraries or binaries independent of the bulk of a main application, as a sort of "danger zone".
- lib - compiles libraries instead of executable binaries, with no main function. By default, uses the app context, and aliases applib. You can create libraries in other contexts by just suffixing the name with lib, such as systemlib.
- app - The full standard library is available, but you can't use raw shifts, jumps, pointers (use refs) or std:bit (bool and std:flags will still use bitwise internally just fine).
- web - Targets the Fissure intermediary language, enabling binary web applications. The full ramifications of this context need to be ironed out through trial and error - it has no file system access, no access to the networking stack besides the convenience http send receive layer, etc.
Besides compiling binaries in various contexts, for security purposes Magma apps need ot specify (in their metadata, or embedded) the various std parts they use and what system resources they access (files, network, contacts, accounts, 3d video, audio, etc) so they can request permissions in a mandatory access control environment seamlessly.
The broad objective is to recognize 5 things:
- Programmers like familiar syntax. If you can get one syntax up an entire stack of languages for various purposes, (which Magma / Fissure / Stratos are intended to be) you can significantly streamline the time investment for new developers to pick up the entire technology paradigm.
- People like readable code. Magma is not only tries to achieve minimal glyphic overhead and readable code, it can be written whitespace significant or not (using traditional curly braces and semicolons) to enable choice.
- People like choice. Choice of paradigm, choice of library, and contexts enable a choice of warning states.
- Times are changing. Heterogeneous computing is going to be huge and massively important, and no modern language is going to have an easy time tacking on easy to use SIMD functionality the way Magma will with std:simd functions, parallel profiling in the compilation stage, etc.
- Build files suck. Qmake, CMake etc - Scons is really neat, but Python (lacking contexts) is hard to get into a nice syntax for compilation instructions. Enter the stratos build context, which is the dialect of Stratos used to write .stob files to build magma binaries. I mean, build systems collectively blow, and none of the modern languages (Go, Rust, etc all have no immediate solution). Make syntax is completely alien to imperative languages (just like shell is completely alien too) and the raw effort to learn them all is absurd and unacceptable for new developers in the coming years, at least to me.
I think we can do better than what we have, and go beyond C++, D, and even Go and Rust, and really recognize the need for a native language you can write anything in and make it simple. Contexts I like to think make this a lot easier than trying to kitchen sink everything into one compiler state and hoping people don't break stuff with preemptive optimizations into inline assembler.
The point of this blog series isn't to write the manfiesto, but to brainstorm aspects of the langauge ideas I have by writing them down.
2013/05/12
Game Rants 3: Neverwinter: Part 2: The Mid Game
The mid game starts in Blacklake and ends in the Northdark. It spans from level 5 to 60, which means it is the super-majority of leveling content. Ends up, that is most of what it is, and that is what I'm touching on here.
Part 2: The Mid Game
Pros:
This contributes to a greater sense that way too much development time and effort was put into this leveling content - from voiced over quests, to well realized zones, to all the different monster models and animations, a lot of this seems like a poor allocation of resources when launching a f2p MMO - people will level once, experience this content once, maybe twice if they level the single alt the game lets you roll without paying money, and then they expect a repeatable end game to keep them playing.
Part 2: The Mid Game
Pros:
- There are options to leveling - foundries, quests, dungeons, skirmishes, and pvp all award xp, so you can level any way you want (in theory).
- Zones have directed stories - you start near the entrance, progress through quest hubs, and eventually reach a dungeon at the end. Every zone has a corresponding big bad in a dungeon to kill, and all these dungeons have epic level 60 versions too.
- Power's are obtained quickly early on and peter out in higher levels to 2 - 3 new abilities every 10 levels with the bulk of powers early on. This means you get the majority of your ability choices quickly, and have actual skill choices early on.
- Most abilities are useful for something, at least on the TR, CR, and Cleric from my experience. There are few stand out "garbage" powers that are useless, but few and far between. In most circumstances, the powers you run are situational, which is good for the choice paradigm.
- Quest dialog and most mobs have voice overs, which help immersion.
- The graphics are great, and the game runs exceptionally well on Arch (which is where I'm running it from).
- Music is great.
- There really isn't much pressure in the pay to win direction while leveling. The level process is really fast so XP boosts are not necessary, and you can easily hit max level just doing the leveling quest content. You can't usually afford a mount as soon as you hit level 20, but you almost always can by 23, and the uselessness of gold for the most part makes it easy to justify the purchase. Bag space is the biggest offender, but you do get 2 bags (one at 10 and one at 30) that somewhat offset the bag pains, and proper inventory management lets you do entire quest hubs, visit the vendor, and repeat ad nauseam without really feeling a space crunch.
- PVP auto levels you to the X9th level in a bracket, so there is no level imbalance. Ability and gear imbalance can contribute somewhat to the experience, but each battle gives half a level and you can get max level pretty quickly through it, so it is pretty balanced leveling pvp. I feel like they learned well from both the failings of the no-balancing WoW and the everyone-to-max level TOR.
- Beyond the visuals, the environments are amazing. Being in the crater of a volcano, or climbing a massive Ice Giant's pick, or battling Gray Wolves beneath a giant flaming Wolf carved mountain all present epic landscapes. The look is great and really conveys strong atmosphere. A real standout was the Chasm, which progressively got more corrupted the deeper you went.
Cons:
- There are very few consistent characters and no overarching story arc in the leveling content. It means there is very little engagement with any zones story because you know the characters are fleeting.
- You don't change the world through your actions. You go places, kill monsters, they will still be there, Helm's Hold is still under demon control, Icespire is still covered in giants, etc - there is no real phasing, so the game doesn't change as you accomplish things, cheapening the engagement even more.
- Quests are rarely dropped, and always require running back to town. This is offset by having multiple progressive objectives in a single quest, but the need to go back to the Protector's Enclave to turn in a completion quest after each zone accentuates this issue - even though you do go back for a reason (usually when you finish a zone, there is another one to progress to).
- While the zones looked awesome, very few environments were manipulable or changed during progression. It was a very static world - besides the mobs standing around waiting to fight, the quest NPCs idling in camps, the world itself is fairly unchanging.
- There is a lot of "free" content here - the leveling process is still drug out, and these zones were all complex works of art that took a lot of effort, but they still reek of unnecessary.
- With the foundry nerf, the only ways to level now are quests and pvp. Dungeons and skirmishes give awful xp per run and for the time commitment, but the gear you get is so fleeting they often aren't worth doing outside the fun of seeing them the first time (which is really fun!). Foundries are almost never worth doing anymore, ever, which I feel hurts one of the games best aspects.
- When leveling up, powers have very nebulous descriptions, and a huge component of how viable an ability is is its animation - how long it is, where it goes, etc. You can't figure this information out yourself, and with respecs costing real money, there is no easy way besides lots of out of game research to figure out how to distribute your limited power points.
- Dungeon roles are questionable. Guardians aren't really needed because most boss mobs spawn tons of adds that a guardian can't aoe tank, barely do any direct damage, and often swing so slowly anyone else can dodge them. Clerics are absolutely mandatory for almost anything past the Crypts, but the dungeon queue system will stick 5 dps in a doomed group instead of requiring a cleric (at least).
- Aggro is very broken. For the most part, it is a combination of distance to target and damage done, but guardians can't outaggro healing aggro, which seems to apply from any distance. This means most mobs can't be pulled off a healing cleric, which makes combat a one dimensional kite rather than a coordinated utilization of classes filling roles.
- This might be a bug moreso than a hard negative, but the group travel mechanics are very annoying. You can't transport between solo player zones and dungeons in a group and it forces you to wait for your party to go almost anywhere. I feel like the entire mechanic is a pointless holdover from D&D proper, and letting people zone in places wouldn't hurt.
The biggest issue with the mid game is the needlessness of it all - the story is too static to be deeply engrossing besides a few rare characters like the lovers that show up in both the Plague Tower and deep Chasm. Because your actions don't have a lasting impact on the world, and the overarching story of catching Valindra takes a backseat after the tutorial and rarely pops up even in passing except in engagements in the city or in the Ebon Downs, the plot is all over the place and leaves players wanting.
This contributes to a greater sense that way too much development time and effort was put into this leveling content - from voiced over quests, to well realized zones, to all the different monster models and animations, a lot of this seems like a poor allocation of resources when launching a f2p MMO - people will level once, experience this content once, maybe twice if they level the single alt the game lets you roll without paying money, and then they expect a repeatable end game to keep them playing.
And f2p depends as much as any game on their persistent players to bring in the new players to spend money and to consistently buy new trinkets as they enter the store. Your hardcore audience is your best money sink, but you don't win them over with a lot of well designed questing zones, because they do those once and never come back.
I feel like the game would have done significantly better on a slashed budget with level 20 as the cap, with you getting 3 power and feat points per level, than having the level 60 cap, all these excessive leveling zones (and let us be honest, the Plague Tower quest chain would have been a great point to finish up at a level cap, and then just add a few levels at a time as new zones are introduced, and have early access to these zones for a few days - of course, deleveling players that go in them and get higher levels so they don't get a power advantage in PVE or PVP until after everyone can enter them). All these zones are just massive developer sinks that absolutely took lots of development time and will produce very little return on investment both in player time engaged in them and in income as a result of them. Like I said earler in this post, there is little incentive to spend money while leveling (the lockboxes I feel are a good exception, and while even the box keys are radically overpriced, a $1.50 a key is much more reasonable for most players to shill out at a whim).
This is even more pronounced at a neutered end game - and, in praise of Cryptic, the leveling content is not short but it isn't excessively long either. It might wane on some players but it won't on most, and the ~60 hours to hit max level finishing the quest content (which many players might not even touch while pvping) is acceptable. My arguments against the quantity of it is directed towards Cryptics bottom line - they spent a lot of time making these beautiful zones with forgettable one off plot threads that few people will be paying money to experience, since they gave it away for free. Kind of like how TOR gave away the best part of that game by making it f2p.
Another issue is the routes to 60 - any group content gives awful XP per run (I feel like completing a dungeon should give at least an entire level in XP, and a skirmish at least half for the time commitment and awful XP returns just killing mobs) but doesn't give enough gear per run or time to justify doing them between quests. If you follow the actual progression the devs laid out in terms of questing content, which is to run a zone, reach the end, do a dungeon, and then move on to another zone, you will outlevel zones in no time and end up being unable to queue for dungeons whose quests at the end of some zones you just reached. Nerfing XP gains isn't an effective tactic either because some people just want to get to end game and making them grind there, even with solid quest content, isn't making them happy paying customers any time sooner.
The foundries were fun and interesting apart from the normal quest content. They would mix a lot less fighting or a lot harder solo content into an otherwise monotonous zoned questing experience, and if not for the exploits around knocking mobs off platforms or farming ogres, they would have been a great complement to the leveling experience. In the next part, I'll go into why the foundries are now completely useless besides the fun they provide (and remember, games are about having fun - and I can't forget to mention I only write this much crap because Neverwinter is, at the end of the day, quite fun. Flawed, which is I what I'm getting to, but still fun).
2013/05/11
Game Rants 3: Neverwinter: Part 1: Intro & The Early Game
This will be a 3 part series on the new MMO by Cryptic, Neverwinter. It entered "open beta" about 2 weeks ago, and I have been playing it with friends pretty much non stop ever since, until a few days ago at max level. This blog arc will be a story of 3 parts, I will discuss the "phases" of the MMO (the early beginning game, the mid game, and the end game). I will speak of what I like, dislike, what could be fixed, and what should be learned as a lesson for future MMOs.
Part 1: The Early Game
Pros:
Part 1: The Early Game
Pros:
- Excellent character customization, including an excellent cascade of customization complexity - you could go with a wide variety of presets, or tweak everything yourself.
- Excellent race selection, and excellent monetization scheme of having paid-for race choices (at launch, drow is locked for a month except for founders).
- Excellent initial class distribution, with a few questionable choices.
- Excellent CGI intro, defining the conflict and setting well.
- The dynamic patching is excellent, enabling players to obtain game assets when needed rather than in raw bulk patches, but the game keeps it an option in the launcher to update immediately everything, which I did overnight after a few days playing and tiring of the ~3 minutes of file patching per zone that was necessary. Excellent tech.
- The opening zone (and the entire game) is voice acted, adding to the atmosphere.
- Visuals are wonderful for an MMO, animations are usually good, soundtrack is excellent.
- Gameplay is good for an action mmo, the best yet in my opinion - animations lock your character in place to add weight, and you have 3 discretized classes of spells - at will (spammable), encounter (short cd), and daily (once per fight usually, require obtaining action points by doing your job to use).
- The intro zone has a dedicated story of getting into the city, introduces the main villain, and shows off the most important game mechanics.
- Visuals on the bridge (mortar fire, fire in general, crumbling expanses) are great.
- The dracolich corpse at the start is a direct tie in to the intro cinematic and is a really good story builder.
- Tutorials are informative - abilities are flashy as you level up and skills appear on your bars, tips tell you the specifics well.
- You are able to write your own story for when people inspect your character.
- Character titles, including two right off the bat from your god and home.
- Moderately good server stability for an MMO launch.
Cons:
- The characters introduced in the cinematic are never touched on again, and the game seriously (in the long run) lacks persistent characters besides Jonas and his wife throughout the spellplague storyline. This starts here with your companion on the bridge, who dies. Barely any other characters are persistent (and the Sargent you constantly report back to in Protector's Enclave is barely characterized after the start).
- The ability scores are really stupid to have in character creation - they aren't actually rolled, there are just ~8 presets of score rolls to pick from. While mousing over them tells you what is "good" for your class, you have no idea what really is the best for you, and any new player is going to be completely lost on what to pick here. Ability scores also matter a lot. The consequence is that a major mechanism of the player is defined off the bat, remains unchangeable forever, and can cripple a character before its even made.
- Abilities are slow to come by - it takes a while to unlock all of your (outrageous!) 8 ability binds (counting the tab class ability). It makes combat really lackluster at the start since you really are only spamming one at will and one encounter and doing nothing else.
- The paragon system in conjunction with the "specialized" classes like great weapon fighter and trickster rogue rather than just fighters and rogues means down the road the game is going to either focus on providing classes with paragon paths (good) or adding excessive classes (bad).
Overall, the beginning is a resounding positive. The combat is sound, the graphics and audio are good, nothing is glaringly wrong - in most bad MMOs, right off the bat bad textures or broken models or crappy combat or lag would hint at flawed games off the bat, but that doesn't happen here. The game has some polish.
My primary criticism of the introduction is a lack of engrossing story - you land on a beach with a hand waived boat crash, you progress through a battlefield doing things for random people, you find a guard who goes with you across a bridge, you find the necromancer from the cinematic, fight a hulk, finish up, and never touch on any of this again. It is a pervasive issue in MMOs that the stories rarely carry weight - the effects of the intro only lead you to the Enclave in the city, and while NPCs will remark you were the one who fought on the bridge, it quickly evaporates and the impact on the story is negligible.
Also, the presence of prerolled sets of ability scores to choose from in my opinion is an unnecessary hold over from D&D proper - yes, ability scores are essential in D&D, but making every class have a forced preset of ability scores (plus a forced level set allocation of scores) would help mitigate a newbie from putting all their points in intellect as a fighter (or taking a sub-optimal initial score roll at character creation). Considering there is no way to re-roll your ability scores even with zen, it becomes an early game core mechanic that can make or break your character.
The issue with class distribution is two fold - for one, your ability to tailor your character to your job is limited if they release classes as specialized roles in combat rather than paragon paths. I've argued in the past why providing few classes with discrete role customization that isn't on the fly changeable like a wow spec is the best way to make each character unique but keep everyone capable of doing a useful job.
In Neverwinter, this is the principle reason there are so few guardian fighters - because they are pure tanks, and no matter what their paragon paths are they will still be a sword and shield fighter, they don't give off an epic appearance like the dps roles, cleric, or gwf. If there were a single fighter, where one paragon path was great weapon fighting and the other was guardian tanking, the shortages of both classes would almost certainly be less severe because more people would have rolled a class that had options in their role.
It seems apparent the reasons for the decision - paragons are about a few powers, not class function in combat. The classes are defined by the weapons they use, and the various styles of using any given weapon set falls into a paragon path (I anticipate at least, given that every class only has one paragon path at launch). But like I said, this means role customization is lacking, and it means everyone is pigeonholed into a single function, and while the cost of leveling alts isn't nearly as bad as it is in other games, the lack of any heirloom-esque items to speed up the process of a reroll (which since the foundry nerf means you are going to be repeating the same content over again each time) which isn't very fun for anyone.
The issue with class distribution is two fold - for one, your ability to tailor your character to your job is limited if they release classes as specialized roles in combat rather than paragon paths. I've argued in the past why providing few classes with discrete role customization that isn't on the fly changeable like a wow spec is the best way to make each character unique but keep everyone capable of doing a useful job.
In Neverwinter, this is the principle reason there are so few guardian fighters - because they are pure tanks, and no matter what their paragon paths are they will still be a sword and shield fighter, they don't give off an epic appearance like the dps roles, cleric, or gwf. If there were a single fighter, where one paragon path was great weapon fighting and the other was guardian tanking, the shortages of both classes would almost certainly be less severe because more people would have rolled a class that had options in their role.
It seems apparent the reasons for the decision - paragons are about a few powers, not class function in combat. The classes are defined by the weapons they use, and the various styles of using any given weapon set falls into a paragon path (I anticipate at least, given that every class only has one paragon path at launch). But like I said, this means role customization is lacking, and it means everyone is pigeonholed into a single function, and while the cost of leveling alts isn't nearly as bad as it is in other games, the lack of any heirloom-esque items to speed up the process of a reroll (which since the foundry nerf means you are going to be repeating the same content over again each time) which isn't very fun for anyone.
But overall the early game is great. It is probably why the MMO is popular right now - you do get a good impression besides the blemishes. In part 2, I'll be harsher, have no fear.
Subscribe to:
Posts (Atom)